package org.chemodansama.fifteen;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

import org.chemodansama.engine.NpSoundMan;
import org.chemodansama.engine.render.NpTexture;

import android.app.Activity;
import android.content.res.AssetManager;

final class GlowRing {
    
    private final static int N = 32; // ring's sides count
    private final static double HALF_WIDTH = 0.5; // half of the ring width
    private final static float LIMIT = 10; // maximum ring radius
    
    private final static long SEC_TO_MILLI  = 1000;
    private final static long MILLI_TO_NANO = 1000000;
    private final static long SEC_TO_NANO   = 1000000000;
    
    private final static long EXPAND_TIME = 750;
    private final static long DELAY = 1250;
    private final static long FULL_TIME = EXPAND_TIME + DELAY;
    
    private final static float EXPAND_IN_SEC = 
        EXPAND_TIME / (float) SEC_TO_MILLI;
    
    private final static long FULL_TIME_NANO = FULL_TIME * MILLI_TO_NANO;
    
    private final static float ACCEL = 
        (float) (2 * LIMIT / Math.sqrt(EXPAND_IN_SEC));
    
    private final float[][] mUnitVecs = new float[N][2];
    private final float[] mVerts = new float[(N + 1) * 4];

    private final FloatBuffer mVertsBuf;
    private final float mCenterX;
    private final float mCenterY;
    
    private double mRad;
    private final long mInitialTime;
    
    GlowRing(float centerX, float centerY) {
        mCenterX = centerX;
        mCenterY = centerY;
        
        for (int i = 0; i < N; i++) {
            double p = i / (double) N * 2 * Math.PI;
            mUnitVecs[i][0] = (float) Math.cos(p);
            mUnitVecs[i][1] = (float) Math.sin(p);
        }
        
        ByteBuffer bb = ByteBuffer.allocateDirect(mVerts.length * 4);
        bb.order(ByteOrder.nativeOrder());
        
        mVertsBuf = bb.asFloatBuffer();
        
        mRad = HALF_WIDTH;
        
        mInitialTime = System.nanoTime();
        
        updateVerts();
    }
    
    private void updateVerts() {
        
        float innerRad = (float) (mRad - HALF_WIDTH);
        float outerRad = (float) (mRad + HALF_WIDTH);
        
        for (int i = 0; i < N + 1; i++) {
            int ui = i % N;
            
            mVerts[i * 4]     = mCenterX + mUnitVecs[ui][0] * innerRad;
            mVerts[i * 4 + 1] = mCenterY + mUnitVecs[ui][1] * innerRad;
            
            mVerts[i * 4 + 2] = mCenterX + mUnitVecs[ui][0] * outerRad;
            mVerts[i * 4 + 3] = mCenterY + mUnitVecs[ui][1] * outerRad;
        }

        mVertsBuf.put(mVerts);
        mVertsBuf.position(0);
    }
    
    int update(double deltaTime) {
        long nt = System.nanoTime();

        double t = (double) ((nt - mInitialTime) % FULL_TIME_NANO) / SEC_TO_NANO;
        
        double r = mRad;
        
        mRad = (HALF_WIDTH + ACCEL * t * t);
        
        updateVerts();
        
        return (r > mRad) ? 1 : 0;
    }
    
    void render(GL10 gl) {
        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        gl.glDisable(GL10.GL_TEXTURE_2D);
        
        gl.glBlendFunc(GL10.GL_DST_ALPHA, GL10.GL_ONE_MINUS_DST_ALPHA);
        
        gl.glVertexPointer(2, GL10.GL_FLOAT, 0, mVertsBuf);
        
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 2 * (N + 1));
        
        gl.glEnable(GL10.GL_TEXTURE_2D);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    }
}

final class GameBackGroundView {
    
    private static final String DIFFUSE_MAP = "textures/15_bg_diffuse.tf";
    private static final String BLEND_MAP = "textures/15_bg_alpha.tf";
    
    private static final float SIZE = 10;
    
    private FloatBuffer mVerts;
    private FloatBuffer mDiffuseTexCoords;
    private FloatBuffer mBlendTexCoords;
    private FloatBuffer mRingTexCoords;
    private ShortBuffer mFaces;
    
    private NpTexture mDiffuse = null;
    private NpTexture mBlendMap = null;
    
    private GlowRing mGlowRing = null;
    
    private final boolean mIsMultiTexSupported;
    private final AssetManager mAssets;
    private final Activity mActivity;
    private final NpSoundMan mSoundMan;
    
    static boolean detectMultiTexSupport(GL10 gl) {
        int[] t = new int[1];
        gl.glGetIntegerv(GL10.GL_MAX_TEXTURE_UNITS, t, 0);
        
        return t[0] > 1;
    }
    
    public GameBackGroundView(Activity activity, AssetManager assets, GL10 gl, 
            float centerX, float centerY) {
        
        mActivity = activity;
        
        mAssets = assets;
        mIsMultiTexSupported = detectMultiTexSupport(gl);
        
        mSoundMan = NpSoundMan.getInstance(mActivity);
        mSoundMan.addSound(R.raw.wavelo);
        
        //======================================================================
        final float[] verts = new float[4 * 2];

        final float scale = (float) Math.sqrt(3); // awesome magic number !!
        
        verts[0] = centerX + SIZE;
        verts[1] = centerY - SIZE;
        
        verts[2] = centerX + SIZE;
        verts[3] = centerY + SIZE;
        
        verts[4] = centerX - SIZE;
        verts[5] = centerY + SIZE;
        
        verts[6] = centerX - SIZE;
        verts[7] = centerY - SIZE;
        
        ByteBuffer bb = ByteBuffer.allocateDirect(verts.length * 4);
        bb.order(ByteOrder.nativeOrder());
        
        mVerts = bb.asFloatBuffer();
        mVerts.put(verts);
        mVerts.rewind();
        
        //======================================================================
        float[] diffuseCoords = new float[4 * 2];
        
        final float tc = 2.5f;
        final float dy = (float) 1 / 6; // another freaking magic num!!! :E
        
        diffuseCoords[0] = tc * scale;
        diffuseCoords[1] = -tc + dy;
        
        diffuseCoords[2] = tc * scale;
        diffuseCoords[3] = tc + dy;
        
        diffuseCoords[4] = -tc * scale;
        diffuseCoords[5] = tc + dy;
        
        diffuseCoords[6] = -tc * scale;
        diffuseCoords[7] = -tc + dy;
        
        bb = ByteBuffer.allocateDirect(diffuseCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        
        mDiffuseTexCoords = bb.asFloatBuffer();
        mDiffuseTexCoords.put(diffuseCoords);
        mDiffuseTexCoords.rewind();
        
        //======================================================================
        float[] blendCoords = new float[4 * 2];
        blendCoords[0] = 1;
        blendCoords[1] = 1;
        
        blendCoords[2] = 1;
        blendCoords[3] = 0;
        
        blendCoords[4] = 0;
        blendCoords[5] = 0;
        
        blendCoords[6] = 0;
        blendCoords[7] = 1;
        
        bb = ByteBuffer.allocateDirect(blendCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        
        mBlendTexCoords = bb.asFloatBuffer();
        mBlendTexCoords.put(blendCoords);
        mBlendTexCoords.rewind();
        
        //======================================================================
        float[] ringCoords = new float[4 * 2];
        ringCoords[0] = 1;
        ringCoords[1] = 1;
        
        ringCoords[2] = 1;
        ringCoords[3] = 0;
        
        ringCoords[4] = 0;
        ringCoords[5] = 0;
        
        ringCoords[6] = 0;
        ringCoords[7] = 1;
        
        bb = ByteBuffer.allocateDirect(ringCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        
        mRingTexCoords = bb.asFloatBuffer();
        mRingTexCoords.put(ringCoords);
        mRingTexCoords.rewind();
        
        //======================================================================
        final short[] faces = new short[4];
        faces[0] = 0;
        faces[1] = 1;
        faces[2] = 2;
        faces[3] = 3;
        
        bb = ByteBuffer.allocateDirect(faces.length * 2);
        bb.order(ByteOrder.nativeOrder());
        mFaces = bb.asShortBuffer();
        mFaces.put(faces);
        mFaces.rewind();
        
        //======================================================================
        
        mGlowRing = new GlowRing(centerX, centerY);
    }
    
    public void loadAssets(GL10 gl) throws IOException {
        mDiffuse = new NpTexture(gl, DIFFUSE_MAP, mAssets, false);
        mBlendMap = new NpTexture(gl, BLEND_MAP, mAssets, false);
    }
    
    public void releaseAssets(GL10 gl) {
        mDiffuse.releaseAssets(gl);
        mBlendMap.releaseAssets(gl);
    }
    
    public void render(GL10 gl) {
        gl.glDisable(GL10.GL_DEPTH_TEST);

        gl.glEnable(GL10.GL_BLEND);
        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
        
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mDiffuseTexCoords);
        mDiffuse.bindGL10(gl);

        if (mIsMultiTexSupported) {
            gl.glClientActiveTexture(GL10.GL_TEXTURE1);
            gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

            gl.glActiveTexture(GL10.GL_TEXTURE1);
            gl.glEnable(GL10.GL_TEXTURE_2D);

            gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mBlendTexCoords);
            mBlendMap.bindGL10(gl);

            gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, 
                         GL10.GL_MODULATE);
        }
          
        gl.glColor4f(0, 0.235f, 1, 1);
        
        gl.glVertexPointer(2, GL10.GL_FLOAT, 0, mVerts);
        gl.glDrawElements(GL10.GL_TRIANGLE_FAN, 4, 
                          GL10.GL_UNSIGNED_SHORT, mFaces);

        gl.glColor4f(1, 1, 1, 1);
        
        if (mIsMultiTexSupported) {
            gl.glDisable(GL10.GL_TEXTURE_2D);
            gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

            gl.glClientActiveTexture(GL10.GL_TEXTURE0);
            gl.glActiveTexture(GL10.GL_TEXTURE0);
        }
        
        mGlowRing.render(gl);
        
        gl.glDisable(GL10.GL_BLEND);
        gl.glEnable(GL10.GL_DEPTH_TEST);
    }
    
    public void update(double deltaTime) {
        if (mGlowRing.update(deltaTime) == 1) {
            mSoundMan.playSound(R.raw.wavelo, 0.5);
        }
    }
}